In [53]:
import plotly.graph_objects as go
from IPython.display import HTML, display

# 1) Gör fig.show() självbärande (inkluderar Plotly JS inline)
def _show_inline(self, *args, **kwargs):
    display(HTML(self.to_html(include_plotlyjs='inline', full_html=False)))
go.Figure.show = _show_inline

# 2) Dölj all kod från start + knapp för att visa/dölj
display(HTML("""
<script>
function toggle_code(){
  const cells = document.querySelectorAll('div.input, .jp-Cell-inputWrapper');
  const hide = !(cells[0] && cells[0].style.display === 'none');
  for (const c of cells){ c.style.display = hide ? 'none' : ''; }
  const b = document.getElementById('codebtn');
  if (b) b.value = hide ? '▶ Visa kod' : '▼ Dölj kod';
}
document.addEventListener('DOMContentLoaded', () => { toggle_code(); }); // dölj från start
</script>
<form style="margin:8px 0 14px;">
  <input id="codebtn" type="button" value="▶ Visa kod" onclick="toggle_code()">
</form>
"""))
In [47]:
import plotly
plotly.offline.init_notebook_mode()
In [39]:
# Imports
import math
import numpy as np
import plotly.graph_objects as go   
import plotly.express as px     
from plotly.subplots import make_subplots
import plotly.graph_objects as go
import numpy as np
import math
In [40]:
# Grundparametrar

slump_seed = 42
inflation = 0.02

startkapital_msek = 7_000           # 7 BSEK
antal_ar = 15
antal_manader = antal_ar * 12
antal_simuleringar = 10_000

# Scenarier: portföljer och alpha
portfoljer = [
    {"namn": "Defensiv portfölj", "mu_ar": 0.045, "sigma_ar": 0.06},
    {"namn": "Hypotes",   "mu_ar": 0.070, "sigma_ar": 0.12},
    {"namn": "Aggressiv portfölj", "mu_ar": 0.085, "sigma_ar": 0.15},
]
alpha_lista = [0.6, 0.7, 0.8]

# Simuleringsfunktion
def simulate_monthly_paths(mu_month, sigma_month, months, n_sims, start_capital,
                           spending_rate, alpha, infl_annual, rng):

    years = months // 12
    paths = np.empty((n_sims, months+1), dtype=float)
    annual_payouts = np.empty((n_sims, years), dtype=float)

    for i in range(n_sims):
        capital = float(start_capital)
        annual_payout = spending_rate * capital       # år 1 baserat på startkapital
        monthly_payout = annual_payout / 12.0
        paths[i, 0] = capital

        for y in range(years):
            annual_payouts[i, y] = annual_payout

            # 12 månadssteg med fast uttag inom året
            for m in range(12):
                idx = y*12 + m + 1
                ret = rng.normal(mu_month, sigma_month)     # aritmetisk månadsreturn
                capital = capital * (1 + ret) - monthly_payout
                paths[i, idx] = capital

            # Hybridregel (inför nästa år)
            target = spending_rate * capital
            annual_payout = alpha * annual_payout * (1 + infl_annual) + (1 - alpha) * target
            monthly_payout = annual_payout / 12.0

    return paths, annual_payouts

# Kör alla scenarier

ss = np.random.SeedSequence(slump_seed)
resultat = {}  

for p in portfoljer:
    mu_ar = p["mu_ar"]
    sigma_ar = p["sigma_ar"]

    # Spending kopplat till mu_ar: nominellt s = mu_ar - inflation (≈ realt konstant uttag)
    spending_rate = mu_ar - inflation

    # Månadsparametrar (aritmetisk omräkning)
    mu_man = (1 + mu_ar) ** (1/12) - 1
    sigma_man = sigma_ar / np.sqrt(12.0)

    for a in alpha_lista:
        rng_scenario = np.random.default_rng(ss.spawn(1)[0])

        banor_manad, arsuttag = simulate_monthly_paths(
            mu_month=mu_man, sigma_month=sigma_man,
            months=antal_manader, n_sims=antal_simuleringar,
            start_capital=startkapital_msek, spending_rate=spending_rate,
            alpha=a, infl_annual=inflation, rng=rng_scenario
        )

        månader = np.arange(0, antal_manader+1)
        median_m = np.median(banor_manad, axis=0)
        p10_m = np.percentile(banor_manad, 10, axis=0)
        p90_m = np.percentile(banor_manad, 90, axis=0)

        år = np.arange(1, antal_ar+1)
        arsuttag_median = np.median(arsuttag, axis=0)
        arsuttag_p10 = np.percentile(arsuttag, 10, axis=0)
        arsuttag_p90 = np.percentile(arsuttag, 90, axis=0)

        resultat[(p["namn"], a)] = {
            "paths": banor_manad,
            "annual_payouts": arsuttag,
            "sammanstallning": {
                "månader": månader,
                "median_m": median_m, "p10_m": p10_m, "p90_m": p90_m,
                "år": år,
                "arsuttag_median": arsuttag_median,
                "arsuttag_p10": arsuttag_p10,
                "arsuttag_p90": arsuttag_p90,
            }
        }
In [48]:
# === Gemensam x-axel (alla scenarier) ===
alla_slutkapital = np.concatenate([
    res["paths"][:, -1] for res in resultat.values()
])
x_min = np.percentile(alla_slutkapital, 1)
x_max = np.percentile(alla_slutkapital, 99)

# === Definiera gemensamma bins ===
nbins = 60
bin_edges = np.linspace(x_min, x_max, nbins + 1)
bin_width = bin_edges[1] - bin_edges[0]

rows = len(portfoljer)
cols = len(alpha_lista)

# === Subplot-titlar = bara alpha ===
subplot_titles = [f"α = {a}" for _ in portfoljer for a in alpha_lista]

fig = make_subplots(
    rows=rows, cols=cols,
    subplot_titles=subplot_titles,
    vertical_spacing=0.08, horizontal_spacing=0.05
)

for i, p in enumerate(portfoljer, start=1):
    mu_pct = 100 * p["mu_ar"]
    sig_pct = 100 * p["sigma_ar"]

    for j, a in enumerate(alpha_lista, start=1):
        res = resultat[(p["namn"], a)]
        slutkapital = res["paths"][:, -1]

        med = np.median(slutkapital)
        p10 = np.percentile(slutkapital, 10)
        p90 = np.percentile(slutkapital, 90)

        # Histogram med fasta bins
        fig.add_trace(
            go.Histogram(
                x=slutkapital,
                xbins=dict(start=x_min, end=x_max, size=bin_width),
                marker_color="steelblue",
                opacity=0.85,
                showlegend=False
            ),
            row=i, col=j
        )

        # Röd linje = startkapital
        fig.add_vline(
            x=startkapital_msek,
            line=dict(color="red", dash="dash"),
            annotation_text="Start",
            row=i, col=j
        )

        # Gemensam skala
        fig.update_xaxes(range=[x_min, x_max],
                         title_text="Slutkapital (MSEK)" if i == rows else None,
                         row=i, col=j)

        # Radetikett = portfölj + μ och σ
        if j == 1:
            fig.update_yaxes(
                title_text=f"{p['namn']} – μ {mu_pct:.1f}% σ {sig_pct:.1f}%",
                row=i, col=j
            )

        # Kort statistiktext
        fig.add_annotation(
            text=f"Med {med:,.0f}<br>P10–P90 {p10:,.0f}–{p90:,.0f}",
            xref=f"x{(i-1)*cols+j}",
            yref=f"y{(i-1)*cols+j}",
            x=0.02, y=0.95,
            xanchor="left", yanchor="top",
            showarrow=False,
            font=dict(size=10, color="black")
        )

# === Layout ===
fig.update_layout(
    height=950,
    width=1150,
    title=f"Fördelning av slutkapital efter {antal_ar} år<br>(rader = portfölj, kolumner = α; spending rate = avkastning - inflation)",
    bargap=0.05,
    template="plotly_white"
)

fig.show()
In [49]:
from plotly.subplots import make_subplots
import plotly.graph_objects as go
import numpy as np

# ===============================
# Sannolikhet att kapital understiger nivåer – grid (rader=portfölj, kolumner=α)
# ===============================
nivåer = [0.9, 0.7, 0.5, 0.3]
palette = {0.9:"#636EFA", 0.7:"#00CC96", 0.5:"#FFA15A", 0.3:"#EF553B"}

rows = len(portfoljer)
cols = len(alpha_lista)
subplot_titles = [f"α = {a}" for _ in portfoljer for a in alpha_lista]

fig = make_subplots(
    rows=rows, cols=cols,
    subplot_titles=subplot_titles,
    vertical_spacing=0.08, horizontal_spacing=0.05
)

# --- välj "rimlig" tick-steg baserat på radens max ---
def pick_dtick(ymax):
    if ymax <= 25: return 5
    if ymax <= 50: return 10
    return 20

# --- beräkna max-sannolikhet per rad (portfölj) ---
row_ymax = []
for p in portfoljer:
    y_max = 0.0
    for a in alpha_lista:
        banor = resultat[(p["namn"], a)]["paths"]
        for t in nivåer:
            y_max = max(y_max, (banor < startkapital_msek*t).mean(axis=0).max()*100)
    y_max = min(100.0, np.ceil(y_max/5)*5)  # runda upp till närmaste 5-tal, cap 100 %
    row_ymax.append(y_max)

# --- rita paneler ---
for i, p in enumerate(portfoljer, start=1):
    mu_pct = 100 * p["mu_ar"]
    sig_pct = 100 * p["sigma_ar"]
    y_cap = row_ymax[i-1]
    dtick = pick_dtick(y_cap)

    for j, a in enumerate(alpha_lista, start=1):
        banor = resultat[(p["namn"], a)]["paths"]
        månader = np.arange(banor.shape[1])

        for t in nivåer:
            sannolikhet = (banor < startkapital_msek*t).mean(axis=0) * 100
            fig.add_trace(
                go.Scatter(
                    x=månader, y=sannolikhet,
                    mode="lines",
                    line=dict(color=palette[t], width=2),
                    name=f"< {int(t*100)} %",
                    showlegend=(i == 1 and j == 1)
                ),
                row=i, col=j
            )

        fig.update_xaxes(
            title_text="Månader" if i == rows else None,
            range=[0, månader[-1]],
            row=i, col=j
        )
        fig.update_yaxes(
            title_text=(f"{p['namn']} – μ {mu_pct:.1f}% σ {sig_pct:.1f}%" if j == 1 else None),
            range=[0, y_cap],
            dtick=dtick,
            row=i, col=j
        )

# --- layout ---
fig.update_layout(
    height=950,
    width=1200,
    template="plotly_white",
    title="Sannolikhet att kapital understiger olika nivåer<br>(rader = portfölj, kolumner = α)",
    legend_title="Kapitalnivå",
    legend=dict(orientation="h", x=0.5, y=-0.08, xanchor="center", yanchor="top"),
    hovermode="x unified",
    font=dict(family="Inter, Segoe UI, Roboto, Arial", size=12),
    margin=dict(l=120, r=40, t=90, b=70)
)

fig.show()
In [50]:
# ===============================
# Fan chart: Årligt uttag – grid (rader=portfölj, kolumner=α)
# ===============================

rows = len(portfoljer)
cols = len(alpha_lista)
subplot_titles = [f"α = {a}" for _ in portfoljer for a in alpha_lista]

fig = make_subplots(
    rows=rows, cols=cols,
    subplot_titles=subplot_titles,
    vertical_spacing=0.08, horizontal_spacing=0.05
)

# Färger
median_color = "#636EFA"                 # blå
outer_fill   = "rgba(99,110,250,0.20)"  # P10–P90
inner_fill   = "rgba(99,110,250,0.35)"  # P25–P75

years_idx = np.arange(1, antal_ar + 1)

def nice_step(ymax, max_ticks=6):
    """Välj ett 'snyggt' tick-steg så att antalet ticks ≲ max_ticks."""
    if ymax <= 0: 
        return 1.0
    # Kandidatsteg i MSEK
    candidates = [0.5, 1, 2, 5, 10, 20, 25, 50, 100, 200, 250, 500, 1000, 2000, 5000]
    for s in candidates:
        if ymax / s <= max_ticks:
            return s
    # Nödfall: grovt steg
    pow10 = 10 ** math.ceil(math.log10(ymax / max_ticks))
    return pow10

# --- Radvis y-topp och tick-steg ---
row_settings = []
for p in portfoljer:
    y_max = 0.0
    for a in alpha_lista:
        payouts = resultat[(p["namn"], a)]["annual_payouts"]  # (n_sims, years)
        y_max = max(y_max, np.percentile(payouts, 90, axis=0).max())
    y_max *= 1.05  # lite luft
    step  = nice_step(y_max, max_ticks=6)
    y_top = math.ceil(y_max / step) * step
    row_settings.append((y_top, step))

# --- Rita paneler ---
for i, p in enumerate(portfoljer, start=1):
    mu_pct  = 100 * p["mu_ar"]
    sig_pct = 100 * p["sigma_ar"]
    y_top, y_step = row_settings[i-1]

    for j, a in enumerate(alpha_lista, start=1):
        payouts = resultat[(p["namn"], a)]["annual_payouts"]

        q10 = np.percentile(payouts, 10, axis=0)
        q25 = np.percentile(payouts, 25, axis=0)
        q50 = np.percentile(payouts, 50, axis=0)
        q75 = np.percentile(payouts, 75, axis=0)
        q90 = np.percentile(payouts, 90, axis=0)

        # Yttre band P10–P90
        fig.add_trace(go.Scatter(x=years_idx, y=q90, mode="lines",
                                 line=dict(width=0), showlegend=False),
                      row=i, col=j)
        fig.add_trace(go.Scatter(x=years_idx, y=q10, mode="lines",
                                 fill="tonexty", fillcolor=outer_fill,
                                 line=dict(width=0),
                                 name="P10–P90", showlegend=(i == 1 and j == 1)),
                      row=i, col=j)

        # Inre band P25–P75
        fig.add_trace(go.Scatter(x=years_idx, y=q75, mode="lines",
                                 line=dict(width=0), showlegend=False),
                      row=i, col=j)
        fig.add_trace(go.Scatter(x=years_idx, y=q25, mode="lines",
                                 fill="tonexty", fillcolor=inner_fill,
                                 line=dict(width=0),
                                 name="P25–P75", showlegend=(i == 1 and j == 1)),
                      row=i, col=j)

        # Median
        fig.add_trace(go.Scatter(x=years_idx, y=q50, mode="lines",
                                 line=dict(color=median_color, width=2.2),
                                 name="Median", showlegend=(i == 1 and j == 1)),
                      row=i, col=j)

        # Axlar
        fig.update_xaxes(title_text="År" if i == rows else None,
                         tickmode="linear", tick0=1, dtick=1,
                         range=[1, antal_ar],
                         row=i, col=j)
        fig.update_yaxes(title_text=(f"{p['namn']} – μ {mu_pct:.1f}% σ {sig_pct:.1f}%" if j == 1 else None),
                         range=[0, y_top], dtick=y_step,
                         tickformat=",.0f",  # heltal MSEK
                         row=i, col=j)

# Gemensam Y-etikett
fig.add_annotation(text="Årligt uttag (MSEK)",
                   xref="paper", yref="paper",
                   x=-0.07, y=0.5, textangle=-90,
                   showarrow=False, font=dict(size=13))

# Layout
fig.update_layout(
    height=950,
    width=1200,
    template="plotly_white",
    title="Fan chart – årligt uttag (rader = portfölj, kolumner = α)",
    legend=dict(orientation="h", x=0.5, y=-0.08, xanchor="center", yanchor="top"),
    hovermode="x unified",
    font=dict(family="Inter, Segoe UI, Roboto, Arial", size=12),
    margin=dict(l=120, r=40, t=90, b=70)
)

fig.show()
In [51]:
# ============
# 100 uttagsbanor (årligt) – grid
# ============
rows = len(portfoljer)
cols = len(alpha_lista)
subplot_titles = [f"α = {a}" for _ in portfoljer for a in alpha_lista]

fig = make_subplots(
    rows=rows, cols=cols,
    subplot_titles=subplot_titles,
    vertical_spacing=0.08, horizontal_spacing=0.05
)

rng = np.random.default_rng(slump_seed)
years_idx = np.arange(1, antal_ar + 1)

# Hjälp: "snyggt" tick-steg
def nice_step(ymax, max_ticks=6):
    if ymax <= 0: return 1.0
    candidates = [0.5,1,2,5,10,20,25,50,100,200,250,500,1000,2000,5000]
    for s in candidates:
        if ymax / s <= max_ticks: return s
    pow10 = 10 ** math.ceil(math.log10(ymax / max_ticks))
    return pow10

# Radvis y-top (95:e percentilen över alla α) för rimlig skala
row_settings = []
for p in portfoljer:
    all_vals = []
    for a in alpha_lista:
        payouts = resultat[(p["namn"], a)]["annual_payouts"]  # (n_sims, years)
        all_vals.append(payouts.ravel())
    all_vals = np.concatenate(all_vals)
    y_max = np.percentile(all_vals, 95) * 1.05
    step  = nice_step(y_max, max_ticks=6)
    y_top = math.ceil(y_max / step) * step
    row_settings.append((y_top, step))

# Rita paneler
for i, p in enumerate(portfoljer, start=1):
    mu_pct  = 100 * p["mu_ar"]
    sig_pct = 100 * p["sigma_ar"]
    y_top, y_step = row_settings[i-1]

    for j, a in enumerate(alpha_lista, start=1):
        payouts = resultat[(p["namn"], a)]["annual_payouts"]  # (n_sims, years)
        n_sims  = payouts.shape[0]
        n_plot  = min(100, n_sims)
        idx     = rng.choice(n_sims, size=n_plot, replace=False)

        # Slumpbanor (tunna grå linjer)
        for k in idx:
            fig.add_trace(
                go.Scatter(
                    x=years_idx, y=payouts[k, :],
                    mode="lines",
                    line=dict(color="rgba(140,140,140,0.35)", width=1),
                    showlegend=False
                ),
                row=i, col=j
            )

        # Median (accent)
        q50 = np.percentile(payouts, 50, axis=0)
        fig.add_trace(
            go.Scatter(
                x=years_idx, y=q50,
                mode="lines",
                line=dict(color="#636EFA", width=2.2),
                name="Median", showlegend=(i == 1 and j == 1)
            ),
            row=i, col=j
        )

        # Axlar
        fig.update_xaxes(
            title_text="År" if i == rows else None,
            tickmode="linear", tick0=1, dtick=1,
            range=[1, antal_ar],
            row=i, col=j
        )
        fig.update_yaxes(
            title_text=(f"{p['namn']} – μ {mu_pct:.1f}% σ {sig_pct:.1f}%" if j == 1 else None),
            range=[0, y_top], dtick=y_step,
            tickformat=",.0f",
            row=i, col=j
        )

# Gemensam Y-etikett
fig.add_annotation(
    text="Årligt uttag (MSEK)",
    xref="paper", yref="paper",
    x=-0.07, y=0.5, textangle=-90,
    showarrow=False, font=dict(size=13)
)

# Layout
fig.update_layout(
    height=950,
    width=1200,
    template="plotly_white",
    title="100 slumpmässiga uttagsbanor – årligt uttag (rader = portfölj, kolumner = α)",
    legend=dict(orientation="h", x=0.5, y=-0.08, xanchor="center", yanchor="top"),
    hovermode="x unified",
    font=dict(family="Inter, Segoe UI, Roboto, Arial", size=12),
    margin=dict(l=120, r=40, t=90, b=70)
)

fig.show()
In [ ]:
 
In [ ]: